package com.hmall.trade.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmall.api.client.CartClient;
import com.hmall.api.client.ItemClient;
import com.hmall.api.dto.ItemDTO;
import com.hmall.api.dto.OrderDetailDTO;
import com.hmall.common.exception.BadRequestException;

import com.hmall.common.utils.UserContext;
import com.hmall.trade.constants.MqConstants;
import com.hmall.trade.domain.dto.OrderFormDTO;
import com.hmall.trade.domain.po.Order;
import com.hmall.trade.domain.po.OrderDetail;
import com.hmall.trade.mapper.OrderMapper;
import com.hmall.trade.service.IOrderDetailService;
import com.hmall.trade.service.IOrderService;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author 虎哥
 * @since 2023-05-05
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements IOrderService {

    //private final IItemService itemService;

    private final ItemClient itemClient;

    private final IOrderDetailService detailService;

    //private final ICartService cartService;

    //private final CartClient cartClient;
    //基于RabbitMQ的异步通知
    private final RabbitTemplate rabbitTemplate;

    @Override
    @GlobalTransactional //全局事务注解
    public Long createOrder(OrderFormDTO orderFormDTO) {
        // 1.订单数据
        Order order = new Order();
        // 1.1.查询商品
        List<OrderDetailDTO> detailDTOS = orderFormDTO.getDetails();
        // 1.2.获取商品id和数量的Map
        Map<Long, Integer> itemNumMap = detailDTOS.stream()
                .collect(Collectors.toMap(OrderDetailDTO::getItemId, OrderDetailDTO::getNum));
        Set<Long> itemIds = itemNumMap.keySet(); //获取所有的键,即获取商品id集合
        // 1.3.查询商品
        //List<ItemDTO> items = itemService.queryItemsByIds(itemIds);
        List<ItemDTO> items = itemClient.queryItemsByIds(itemIds);
        if (items == null || items.size() < itemIds.size()) {
            throw new BadRequestException("商品不存在");
        }
        // 1.4.基于商品价格、购买数量计算商品总价：totalFee
        int total = 0;
        for (ItemDTO item : items) {
            total += item.getPrice() * itemNumMap.get(item.getId());
        }
        order.setTotalFee(total);
        // 1.5.其它属性
        order.setPaymentType(orderFormDTO.getPaymentType());
        order.setUserId(UserContext.getUser());
        order.setStatus(1);
        // 1.6.将Order写入数据库order表中
        save(order); //IService接口定义的方法

        // 2.保存订单详情
        List<OrderDetail> details = buildDetails(order.getId(), items, itemNumMap);
        detailService.saveBatch(details);

        // 3.清理购物车商品
        //cartService.removeByItemIds(itemIds);
        //基于OpenFeign的**清理购物车同步调用
        //cartClient.removeByItemIds(itemIds);
        Map<String, Object> messageMap = new HashMap<>();
        //基于RabbitMQ的异步通知
        try {

/*           //法一：使用Map封装的方式传递用户ID
            messageMap.put("itemIds", itemIds);
            messageMap.put("userId",UserContext.getUser());
            rabbitTemplate.convertAndSend(
                    "trade.topic",
                    "order.create",
                    messageMap
            );*/

            //法二：使用请求头的方式接受传递用户ID
/*            rabbitTemplate.convertAndSend(
                    "trade.topic",
                    "order.create",
                    itemIds
                   , new MessagePostProcessor() {
                        @Override
                        public Message postProcessMessage(Message message) throws AmqpException {
                            Long userId = UserContext.getUser();  // 获取用户ID
                            log.info("发送清理购物车消息，用户id:{}", userId);
                            message.getMessageProperties().setHeader("userId", userId);  // 设置消息头部
                            return message;  // 返回处理后的消息
                        }
                    }
            );*/

            /*
             * 法三：使用静态代理的方式，MessageConverterProxy
             * 通过静态代理模式来增强 `Jackson2JsonMessageConverter` 的功能，
             * 从而在其核心方法 `toMessage` 和 `fromMessage` 上进行扩展。
             * 例如，可以增强它的功能，将 `userId` 自动注入到消息头中，
             * 或者从消息头中提取 `userId` 并存储到 `UserContext`。
             *
             */
            rabbitTemplate.convertAndSend(
                    "trade.topic",
                    "order.create",
                    itemIds
            );

        } catch (Exception e) {
            log.error("清理购物车失败", e);
        }


        // 4.扣减库存
        try {
            //itemService.deductStock(detailDTOS);
            itemClient.deductStock(detailDTOS);
        } catch (Exception e) {
            throw new RuntimeException("库存不足！");
        }


        // 5.发送延迟消息，在设置的延迟时间后，自动检测订单支付状态
        rabbitTemplate.convertAndSend(
                MqConstants.DELAY_EXCHANGE_NAME,
                MqConstants.DELAY_ORDER_KEY,
                order.getId(),
                new MessagePostProcessor() {
                    @Override
                    public Message postProcessMessage(Message message) throws AmqpException {
                        message.getMessageProperties().setDelay(10000); //设置订单消息的延迟时间,即为订单超时的时间
                        return message;
                    }
                }
        );

        return order.getId();
    }

    @Override
    @Transactional
    public void markOrderPaySuccess(Long orderId) {
        Order order = new Order();
        order.setId(orderId);
        order.setStatus(2);
        order.setPayTime(LocalDateTime.now());
        updateById(order);
    }

    /**
     * 取消订单，并回复库存
     *
     * @param orderId
     */
    @Override
    public void cancelOrder(Long orderId) {
        //0.将订单状态修改为已关闭
        Order order = new Order();
        order.setId(orderId);
        order.setStatus(5); //交易取消，订单关闭
        updateById(order);

        //1.获取订单详情-商品id和对应的数量
        List<OrderDetail> details =
                detailService.list(
                        new LambdaQueryWrapper<OrderDetail>().eq(OrderDetail::getOrderId, orderId)
                );

        List<OrderDetailDTO> detailDTOS = new ArrayList<>(details.size());
        //2.回复对应商品的库存
        for (OrderDetail detail : details) {
            OrderDetailDTO orderDetailDTO = new OrderDetailDTO();
            BeanUtil.copyProperties(detail, orderDetailDTO);
            detailDTOS.add(orderDetailDTO);
        }
        if (CollectionUtil.isEmpty(detailDTOS)) {
            log.warn("订单详情为空，不需要恢复库存, orderId={}", orderId);
            return;
        }
        itemClient.replyStock(detailDTOS);

/*        //0.删除订单和订单详情
        detailService.remove(
                new LambdaQueryWrapper<OrderDetail>().eq(OrderDetail::getOrderId, orderId)
        );
        removeById(orderId);*/
    }

    /**
     * 构建订单详情
     *
     * @param orderId // 订单id
     * @param items   // 商品列表
     * @param numMap  // 商品数量Map
     * @return
     */
    private List<OrderDetail> buildDetails(Long orderId, List<ItemDTO> items, Map<Long, Integer> numMap) {
        List<OrderDetail> details = new ArrayList<>(items.size());
        for (ItemDTO item : items) {
            OrderDetail detail = new OrderDetail();
            detail.setName(item.getName());
            detail.setSpec(item.getSpec());
            detail.setPrice(item.getPrice());
            detail.setNum(numMap.get(item.getId()));
            detail.setItemId(item.getId());
            detail.setImage(item.getImage());
            detail.setOrderId(orderId);
            details.add(detail);
        }
        return details;
    }
}
